// -*- c-basic-offset: 2 default-tab-width: 2 indent-tabs-mode: t -*-
// vim: autoindent tabstop=2 noexpandtab shiftwidth=2 softtabstop=2


/*
 *
 *     This file is in C and must be in C!
 *
 *   First because C is beautiful
 *
 *   Second because we are linking this file in the R Compiler (in the native R evaluator) to generate the bytecodes
 *
 */

#define __BC_C
#include "bc.h"
#include <string.h>
#undef __BC_C

int write_const(cpool_entry *pe, FILE *fd){
	int type = pe->type;
	int size_entry;
	int written = fwrite(&type, sizeof(int), 1, fd);

	switch(type){
		case CPOOL_NULL:
			return written;
		case CPOOL_STR:
			written += fwrite(&pe->data.pe_char.size, sizeof(int), 1, fd);
			written += fwrite(pe->data.pe_char.value, sizeof(char), pe->data.pe_char.size, fd);
			return written;
			break;
		case CPOOL_CPXSXP:
				size_entry = sizeof(Rcomplex);
				break;
		case CPOOL_DBLSXP:
				size_entry = sizeof(double);
				break;
		case CPOOL_VECSXP:
		case CPOOL_STRSXP:
		case CPOOL_LGLSXP:
		case CPOOL_INTSXP:
				size_entry = sizeof(int);
				break;
	}
	written += fwrite(&pe->data.pe_vector.size, sizeof(int), 1, fd);
	written += fwrite(&pe->data.pe_vector.names, sizeof(int), 1, fd);
	written += fwrite(pe->data.pe_vector.values, size_entry, pe->data.pe_vector.size, fd);
	return written;
}

int read_const(cpool_entry *pe, FILE *fd, void * (alloc) (size_t)){
	int len;
	int type;
	int read = fread(&type, sizeof(int), 1, fd);
	pe->type = type;

	if(type == CPOOL_NULL) // no data associated to null
		return read;
	read += fread(&len, sizeof(int), 1, fd);
	if(type == CPOOL_STR){
			pe->data.pe_char.size = len; // ... maybe need to move size out of data
			pe->data.pe_char.value = alloc(len*sizeof(char)+1); // +1 for the null to have a C compatibility
			read += fread(pe->data.pe_char.value, sizeof(char), len, fd);
			pe->data.pe_char.value[len] = 0;
			return read;
	}
	pe->data.pe_vector.size = len; // ... maybe need to move size out of data
	read += fread(&pe->data.pe_vector.names, sizeof(int), 1, fd);
	switch(pe->type){
		case CPOOL_CPXSXP:
			pe->data.pe_cplx.values = alloc(len*sizeof(Rcomplex));
			read += fread(pe->data.pe_cplx.values, sizeof(Rcomplex), len, fd);
			break;
		case CPOOL_DBLSXP:
			pe->data.pe_real.values = alloc(len*sizeof(double));
			read += fread(pe->data.pe_real.values, sizeof(double), len, fd);
			break;
		case CPOOL_LGLSXP:
		case CPOOL_INTSXP:
		case CPOOL_STRSXP:
		case CPOOL_VECSXP:
			pe->data.pe_vector.values = alloc(len*sizeof(int)); 
			read += fread(pe->data.pe_vector.values, sizeof(int), len, fd);
			break;
		default:
			// error(3, "Unknown constant pool type: %x (%d)\n", pe->type, pe->type);
			return -1;
	}
	return read;
}

int read_funinfos(int *size, fun_info **infos, FILE *fd, void * (alloc) (size_t)){
  int len;
  int read = fread(&len, sizeof(int), 1, fd);
  *size = len;
  *infos = alloc(sizeof(fun_info) * len);
  read += fread(*infos, sizeof(fun_info), len, fd);
  return read;
}

int write_funinfos(int size, fun_info *infos, FILE *fd){
	int written = fwrite(&size, sizeof(int), 1, fd);
	written += fwrite(infos, sizeof(fun_info), size, fd);
	return written;
}

int read_segment(int *size, code_segment *segment, FILE *fd, void * (alloc) (size_t)){
	int len;
	int read = fread(&len, sizeof(int), 1, fd);
	*size = len;
	*segment = alloc(sizeof(int) * len);
	//read += fread(&segment->param_names_pe, sizeof(int), 1, fd);
	//read += fread(&segment->param_values_pe, sizeof(int), 1, fd);
	read += fread(*segment, sizeof(int), len, fd);
	return read;
}

int write_segment(int size, code_segment segment, FILE *fd){
	int written = fwrite(&size, sizeof(int), 1, fd);
	//written += fwrite(&segment->param_names_pe, sizeof(int), 1, fd);
	//written += fwrite(&segment->param_values_pe, sizeof(int), 1, fd);
	written += fwrite(segment, sizeof(int), size, fd);
	return written;
}

int write_bc_file(FILE *fd, bc_header *header, cpool_entry *cpool, fun_info *infos, code_segment cs){
	int written = fwrite(header, sizeof(bc_header), 1, fd);
	for(int i = 0; i < header->pp; i++)
		written += write_const(cpool+i, fd);
	written += write_funinfos(header->funinfos, infos, fd);
	written += write_segment(header->cs, cs, fd);
	return written;
}

int read_bc_file(FILE *fd, bc_header *header, cpool_entry **cpool, fun_info **f_infos, code_segment *cs, void * (alloc) (size_t)){
	int read = fread(header, sizeof(bc_header), 1, fd);
	if(strncmp(header->magic, BC_MAGIC, 4)) return -1;
	if(!cpool) return read;
	*cpool = alloc(sizeof(cpool_entry) * header->pp);
	for(int i = 0; i < header->pp; i++)
		read += read_const((*cpool)+i, fd, alloc);
	if(!f_infos) return read;
	read += read_funinfos(&header->funinfos, f_infos, fd, alloc);
	if(!cs) return read;
	//for(int i = 0; i < header->segmentInUse; i ++)
	read += read_segment(&header->cs, cs, fd, alloc);
	return read;
}

#define PRINT_ARRAY(prefix, base, format) \
	do {\
		print(prefix "(%d):%.2x: ", base.size, (base.names>0) ? base.names : 0);\
		for(int i = 0; i <  base.size; i++)\
				print("%s" format, i ? ", ": "[ ", base.values[i]);\
		if(base.size > 0) print(" ]");\
	}while(0)

void dumpPE(cpool_entry *pe, void (print)(const char*, ...)){
	int type;
	switch(type = pe->type){
		case CPOOL_NULL:
			print("NULL");
			break;
		case CPOOL_STR:
			print("CHAR(%d):%s", pe->data.pe_char.size, pe->data.pe_char.value);
			break;
		case CPOOL_STRSXP:
			PRINT_ARRAY("STR", pe->data.pe_vector, "0x%.4x");
			break;
		case CPOOL_VECSXP:
			PRINT_ARRAY("VEC", pe->data.pe_vector, "0x%.4x");
			break;
		case CPOOL_LGLSXP:
			PRINT_ARRAY("LGL", pe->data.pe_lgl, "%d");
			break;
		case CPOOL_INTSXP:
			PRINT_ARRAY("INT", pe->data.pe_int, "%d");
			break;
		case CPOOL_DBLSXP:
			PRINT_ARRAY("DBL", pe->data.pe_real, "%lg");
			break;
		case CPOOL_CPXSXP:
			PRINT_ARRAY("CPX", pe->data.pe_cplx, "%lg");
			break;
		default:
			print("ERROR: no print for %d", type);
	}
}

void dumpPool(int cpool_len, cpool_entry *pe, void (print)(const char*, ...)){
	for(int i = 0 ; cpool_len --; i ++){
		print("0x%x:", i);
		dumpPE(pe+i, print);
		print("\n");
	}
}

void dumpBC(int size, code_segment seg, void (print)(const char*, ...)){
	dumpSegmentHeader(size, seg, print);
	dumpMyBC(seg, size, print);
}

void dumpMyBC(code_segment cs, int len, void (print) (const char*, ...)){
	int i;
	for(i = 0; i < len; i++){
		if((i%4) == 0)
			print("%.4x: ", i);

		int e = cs[i];
		print("%.2x %.2x  %.2x %.2x   ", (e >> 24), ((e >> 16) & 0xFF), ((e >> 8) & 0xFF), (e & 0xFF));
		if((i%4) == 3)
			print("\n");
	}
	if((i%4))
		print("\n");
}

void dumpSegmentHeader(int size, code_segment segment, void (print) (const char*, ...)){
	print("[Len: 0x%.4x (%.6d)]\n",
			size, size);
}

void dumpBCHeader(bc_header *header, void (print) (const char*, ...)){
	print("Constant pool size: 0x%x (%d)\t[%4.4s]\n", header->pp, header->pp, header->magic);
}

void disassemble(int size, code_segment segment, cpool_entry *cpool, void (print) (const char*, ...)){
	dumpSegmentHeader(size, segment, print);
	int nbarg;
	for(int pc = 0; pc < size; pc++){
		unsigned long bc = segment[pc];
		if(bc <= sizeof(BC_names)){
			print("%.4x: %s", pc, BC_names[bc]);
			nbarg = BC_args[bc];
			while(nbarg){
				bc = segment[++pc];
				switch(nbarg & BCPMASK){
					case IMM:
						print("\t#0x%x", bc);
						break;
					case IFI:
						print("\t[%x]", bc);
						break;
					case LBL:
						print("\t:0x%x", bc);
						break;
					case IOL:
					case IDX:
						print("\t0x%x /* ", bc);
						dumpPE(cpool+bc, print);
						print(" */");
						break;
				}
				nbarg >>= BCPLEN;
			}
			print("\n");
		} else
			print("%.4x: ERROR %x\n", pc, bc);
	}
}
